package com.inmotion.flutter.plugin.jieli_ble_ota.jieli_ble_ota_plugin;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

import androidx.annotation.NonNull;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

/**
 * JieliBleOtaPlugin
 */
public class JieliBleOtaPlugin implements FlutterPlugin, MethodCallHandler {

    // methods
    static final String methodStart2Ota = "inmotion://jieli_ota/method_start_to_ota";
    static final String methodRequestDeviceFirmwareState = "inmotion://jieli_ota/method_request_fw_state";
    static final String methodNotifyDataReceived = "inmotion://jieli_ota/method_on_msg_received";
    static final String methodNotifyConnected = "inmotion://jieli_ota/method_on_link_connected";
    static final String methodNotifyDisconnected = "inmotion://jieli_ota/method_on_link_disconnected";

    // callbacks
    static final String cbWriteData = "inmotion://cb/write_data";
    static final String cbDisconnectLink = "inmotion://cb/link/disconnect";
    static final String cbConnectLink = "inmotion://cb/link/connect";
    static final String cbOnOtaProgressChanged = "inmotion://cb/on_ota_progress_changed";
    static final String cbOnDeviceFirmwareStatusRequested = "inmotion://cb/on_device_firmware_status_requested";
    static final String cbOnDeviceFirmwareStatusRequestFailed = "inmotion://cb/on_device_firmware_status_request_failed";
    static final String cbOnOtaUpdateFailed = "inmotion://cb/on_ota_update_failed";
    static final String cbOnOtaUpdateSuccess = "inmotion://cb/on_ota_update_success";

    private MethodChannel channel;

    private JieliBleOtaManager otaManager;

    public Context appContext;

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "jieli_ble_ota_plugin");
        channel.setMethodCallHandler(this);

        appContext = flutterPluginBinding.getApplicationContext();
        otaManager = new JieliBleOtaManager(flutterPluginBinding.getApplicationContext(), this, new BleHandler(this));
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        switch (call.method) {
            case methodStart2Ota:
                startOTA(call.arguments());
                break;
            case methodRequestDeviceFirmwareState:
                requestDeviceFirmwareState();
                break;
            case methodNotifyDataReceived:
                notifySDKDataReceived(call.arguments());
                break;
            case methodNotifyConnected: {
                String identifier = call.argument("identifier");
                String name = call.argument("name");
                notifySDKDeviceConnected(identifier, name);
                break;
            }
            case methodNotifyDisconnected: {
                String identifier = call.argument("identifier");
                String name = call.argument("name");
                notifySDKDeviceDisconnected(identifier, name);
                break;
            }
            default:
                Log.d("JieliBleOtaPlugin", "onMethodCall: Unknown method call [" + call.method + "].");
                break;
        }
    }

    private void requestDeviceFirmwareState() {
        otaManager.requestFirmwareState();
    }

    private void startOTA(String file) {
        otaManager.startOTA(file);
    }

    private void notifySDKDeviceConnected(String identifier, String name) {
        otaManager.notifyDeviceConnectionChanged(true, identifier, name);
    }

    private void notifySDKDeviceDisconnected(String identifier, String name) {
        otaManager.notifyDeviceConnectionChanged(false, identifier, name);
    }

    private void notifySDKDataReceived(byte[] bytes) {
        otaManager.notifyDataReceived(bytes);
    }

    public void onWriteBytesRequest(byte[] bytes) {
        channel.invokeMethod(cbWriteData, buildParams("bytes", bytes));
    }

    public void onConnectRequest(String address, String name) {
        channel.invokeMethod(cbConnectLink, buildParams("address", address, "name", name));
    }

    public void onDisconnectRequest() {
        channel.invokeMethod(cbDisconnectLink, null);
    }

    public void onFirmwareStateRequested(int result, boolean isFirmwareBroken) {
        if (result == 0) {
            channel.invokeMethod(cbOnDeviceFirmwareStatusRequested, buildParams("is_firmware_broken", isFirmwareBroken));
        } else {
            channel.invokeMethod(cbOnDeviceFirmwareStatusRequestFailed, buildParams("error_code", result));
        }
    }

    public void onOTAProgressUpdated(int type, float progress) {
        channel.invokeMethod(cbOnOtaProgressChanged, buildParams("type", type, "progress", progress));
    }

    public void onOTAUpdateSuccess() {
        channel.invokeMethod(cbOnOtaUpdateSuccess, null);
    }

    public void onOTAUpdateFailed(int code, int subCode, int opCode) {
        channel.invokeMethod(cbOnOtaUpdateFailed, buildParams("error_code", code + "|" + subCode + "|" + opCode));
    }

    private static Map<String, Object> buildParams(Object... keyValues) {
        Map<String, Object> params = new HashMap<>();
        if (keyValues == null || keyValues.length == 0) return params;
        if (keyValues.length % 2 != 0) {
            throw new IllegalArgumentException("Keys and values must be passed in as pair.");
        }
        for (int i = 0; i < keyValues.length; i += 2) {
            params.put((String) keyValues[i], keyValues[i + 1]);
        }
        return params;
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
        otaManager.release();
        otaManager = null;
    }

    static class BleHandler extends JieliBleOtaManager.BleHandler {
        JieliBleOtaPlugin plugin;

        Handler uiThreadHandler = new Handler(Looper.getMainLooper());

        public BleHandler(JieliBleOtaPlugin plugin) {
            this.plugin = plugin;
        }

        @Override
        void writeBytes(byte[] bytes) {
            uiThreadHandler.post(() -> plugin.onWriteBytesRequest(bytes));
        }

        @Override
        void connect(String address, String name) {
            uiThreadHandler.post(() -> plugin.onConnectRequest(address, name));
        }

        @Override
        void disconnect() {
            Log.d("BleHandler", "request flutter plugin to disconnect");
            uiThreadHandler.post(() -> plugin.onDisconnectRequest());
        }
    }
}
